PHPStan | Larastan | ergebnis
level10のエラーと戦う
level5とも戦うことにした
Laravelにおけるメソッドの@returnの指定について
超基礎から
https://qiita.com/programhunting/items/e9489c994322c55fe8f8
code:php
public function test(int $number): array
{
$array = $number, 2, 3;
return $array;
}
test() return type has no value type specified in iterable type array.
PHPDocで、丁寧に指定してやる必要がある。
この場合は、こう
code:php
/**
* @return array<int>
*/
この場合は?
code:php
public function test(string $str): array
{
$array = $str, 2, 3;
code:php
/**
* @return array<int|string>
*/
こうである
連想配列にしてみる
code:php
public function test(int $number): array
{
$array = [
'id' => $number,
'name' => 'skoni',
];
test() should return array<int> but returns array<string, int|string>.
code:php
$array = [
'id' => $number, // 文字列インデックス 'id'、値は $number(int)
'name' => 'skoni', // 文字列インデックス 'name'、値は 'skoni'(string)
];
インデックスが'id'など→stringで、値の部分にint、stringが存在する。 なのでこれは、
code:php
/**
* @return array<string,int|string>
*/
これで良い
さらに複雑にしてみる
code:php
public function test(int $number): array
{
if ($number === 0) {
$array = [
'id' => $number,
'name' => 'skoni',
];
} else {
$array = 1, 2, 3;
}
return $array;
}
test() should return array<string, int|string> but returns array<int|string, int|string>.
条件ごとに配列の構造が異なる。
※そもそもこの形は、実装の仕方としては良くないということになる
この場合は、array<string, int|string>とarray<int>の可能性がある
指定の通りにしてみる@return array<int|string, int|string>
int|stringになっている
これはelse側のintの形をカバーした書式になっている
これは推奨される書き方とは言い難い。本当は|などを使って書き分けた方が良い
code:php
/**
* @return array<string, int|string>|array<int, int>
*/
public static function test(int $number): array
{
if ($number < 5) {
$array = [
'id' => $number,
'name' => 'skoni',
];
} else {
$array = 1, 2, 3;
}
return $array;
}
なので、このようにした方がわかりやすい
Laravelっぽく書いた場合
code:php
public static function test(int $number): Collection
{
$model = self::where('id', $number)->get();
return $model;
}
エラー
74 Method App\Models\Test::test() return type with generic class Illuminate\Database\Eloquent\Collection does not specify its types: Key, Model
Collectionはジェネリッククラスであるため、キーの方とコレクションに格納されているモデルの型を指定していないことで発生している
コレクションの型パラメータを明示的に指定することで、PHPStanに対してTestモデルであることを示す必要がある
コメントで指定してやる
例2
code:php
/**
* @return Collection<int, Sample>
*/
public static function test(int $number): Sample
{
$model = Sample::where('id', $number)->get();
return $model;
}
この場合、エラーが2つでる
1. PHPDoc tag @return with type Illuminate\Database\Eloquent\Collection<int, App\Models\Sample> is incompatible with native type App\Models\Sample.
2. test() should return App\Models\Sample but returns Illuminate\Database\Eloquent\Collection<int, App\Models\Sample>.
まず返り値として: Sampleを指定しているが、実際はコレクションであるので間違い
これを直せばどちらも解消する。
code:php
use Illuminate\Database\Eloquent\Collection;
/**
* @return Collection<int, Sample>
*/
public static function test(int $number): Collection
{
$model = Sample::where('id', $number)->get();
return $model;
}
ちなみにintはコレクションのインデックスのこと
何が入っているかは、dd()でわかる
https://scrapbox.io/files/67695c0f09da145f72be01c5.png
実務とかでの考え方
取り急ぎmixedて定義して、その周りでPHPStanでエラーが出ないようならmixedから改めて定義してあげるのが精神的にも良さそうである
遭遇したエラーについて
code:php
public static function test(int $id): array
{
$model = self::find($id);
$list = [];
if (! is_null($model)) {
$relate = $model->Other;
if (! is_null($relate)) {
// ...
Call to function is_null() with Illuminate\Database\Eloquent\Collection<int, App\Models\Sample> will always evaluate to false.
if (! is_null($relate)) {が冗長である
事前に$modelのnullチェックをしているから、$relateのnullチェックの必要はないということか?
書かなくても常にfalseを返すでしょ、と怒られてる
もしかしたら中身は何もないかもしれないが、リレーションのモデルは常に返る
mixedとして認識されるエラー
code:php
if (! is_null($model)) {
$relate = $model->relate;
$relate = $relate->map(function ($item) {
return [
'id' => $item->id,
'name' => $item->name,
];
})
->toArray();
$businessLineList = [
'model_name' => $model->name,
'relates' => $relate,
];
}
57 Method App\Models\Test::test() should return array<string, array<int, array<string, int|string>>|string> but returns array<string, array<mixed>|string>.
🪪 return.type
ちゃんと返りの型は指定した通りで正しいのだが、mixedで認識されている
'id' => (int)$item->id,などとしても良いのだが、この場合もエラーになることがある。
46 Casting to int something that's already int.
mapが悪さしてるっぽいので、こんな感じに書き直してあげると治った
code:php
if (! is_null($model)) {
$relate = $model->relate;
$formattedRelate = [];
foreach ($relate as $item) {
$formattedRelate[] = [
'id' => $item->id,
'name' => $item->name,
];
}
$businessLineList = [
'name' => $model->name,
'relates' => $formattedRelate,
];
}
PHPの文法だけで書けるものは、なるべくPHPの文法で書いた方が良い
mixed given
code:php
Parameter #1 $id of static method App\Models\Sample::getA() expects int, mixed given.
特に、フォームで渡されたリクエストデータはmixed(またはstring)であるため証明が必要である
(int)やintval()でキャストしようとしてもCannot cast mixed to int. エラーに遭遇する。
フォームリクエスト側で調整するか、
https://qiita.com/k_takaya/items/37971b13e4add480511a
filterでうまく証明させよう
https://blog.ichikaway.com/entry/2023/07/15/phpstan-intval
code:php
/**
* 引数として渡した値がint型であることを保証させる
*/
protected function ensureInteger(mixed $value): int
{
$integerValue = filter_var($value, FILTER_VALIDATE_INT);
if ($integerValue === false) {
throw new \InvalidArgumentException('Invalid input: Expected an integer but received '.var_export($value, true));
}
return $integerValue;
}
$formId = $this->ensureInteger($formData'id');
$name = Sample::getNameFromId($formId); // $argument int想定
アップロードファイル操作時の警告
code:php
# アップしたファイルを保存する処理
public function confirm(Request $request): View
{
$formData = $request->all();
$pdfFile = $request->file('pdf_file'); // inputタグのnameの値
$originalFileName = $pdfFile->getClientOriginalName();
$uniqueFileName = uniqid('pdf_', true).'.'.$pdfFile->getClientOriginalExtension();
$pdfFile->storeAs($tempPath, $uniqueFileName, 'local');
69 Cannot call method getClientOriginalName() on array<int,Illuminate\Http\UploadedFile>|Illuminate\Http\UploadedFile|null.
70 Cannot call method getClientOriginalExtension() on array<int,Illuminate\Http\UploadedFile>|Illuminate\Http\UploadedFile|null.
71 Cannot call method storeAs() on array<int, Illuminate\Http\UploadedFile>|Illuminate\Http\UploadedFile|null.
同じエラーがたくさん出る
https://zenn.dev/wadakatu/scraps/6bf95f8d1611bc
これは、PDFファイルがアップロードされない可能性があるから
Illuminate\Http\UploadedFile>|Illuminate\Http\UploadedFile|null
code:php
# アップしたファイルを保存する処理
public function confirm(Request $request): View
{
$formData = $request->all();
$pdfFile = $request->file('pdf_file'); // inputタグのnameの値
if ($pdfFile instanceof \Illuminate\Http\UploadedFile) {
$originalFileName = $pdfFile->getClientOriginalName();
$uniqueFileName = uniqid('pdf_', true).'.'.$pdfFile->getClientOriginalExtension();
$pdfFile->storeAs($tempPath, $uniqueFileName, 'local');
} else {
throw new \RuntimeException('PDFファイルがアップロードされていません。');
}
...
instanceofで、UploadedFileであることを保証してやると良い
https://www.php.net/manual/ja/language.operators.type.php
ちょっとずつ分かってきたかもしれない
バリデーション部分などのエラー
withValidatorなどで自作のバリデーションを作ったとする
code:php
public function withValidator(Validator $validator): void
{
$validator->after(function ($validator) {
...
if ($is_appropriate === false) {
$validator->errors()->add(
'type',
'値が適切ではありません。'
);
}
});
}
Line Http/Requests/SelfRequest.php
------ -------------------------------------------
120 Cannot call method add() on mixed.
🪪 method.nonObject
120 Cannot call method errors() on mixed.
🪪 method.nonObject
$validatorの型を正確に推測できていない。
Validator $validatorとして定義しているはずだが...
コールバック関数で渡す場合も、明示的にしてやる必要があった。
$validator->after(function (Validator $validator) {
これで解決
Cannot cast mixed to int.
mixedというのは、それまで型がチェックされていない値のことを指すので、型のチェックをしてやれば良い
code:php
// $kindIdは、フォームなどから取得した値
$result[] = [
'id' => (int)$kindId,
];
Cannot cast mixed to int.
code:php
if (is_string($kindId)) {
$kindId = (int) $kindId;
}
こんな感じでmixedではなく、stringだよ〜というように伝えてやれば良い
Language construct isset() should not be used.
ergebnisのエラー
https://github.com/ergebnis/phpstan-rules?tab=readme-ov-file#expressionsnoissetrule
code:php
if (isset($data'id')) {
データにidというキーを持つかどうか調べているif文。こういう場合は
code:php
if (array_key_exists('id', $data)) {
こう書けばよい
with incorrect case: rollback
code:php
DB::rollback()
大文字と小文字の区別がないせい。
DB::rollBack()と書けばOK
covariantエラー
code:txt
PHPDoc type array of property App\Exceptions\Handler::$dontReport is not covariant with PHPDoc type array<int, class-string<Throwable>> of overridden property Illuminate\Foundation\Exceptions\Handler::$dontReport.
🪪 property.phpDocType
@varで定義している値が、親クラスのものと違う
extendsしているファイルなら、親ファイルで記載しているdocコメントと同一のものを指定してやろう。
Auth関連
code:php
$user = Auth::user();
$profile = Profile::create('user_id' => $user->id);
# Access to an undefined property Illuminate\Contracts\Auth\Authenticatable::$id.
$userのクラスを誤認している
code:php
/** @var \App\User $user */
$user = Auth::user();
こちらでOK
Debugbarのコードについて
code:php
Debugbar::debug($savedata, count($selected_info));
# Static method Barryvdh\Debugbar\Facades\Debugbar::debug() invoked with 2 parameters, 1 required.
パラメータを1つで渡すべきものに、複数渡してしまっている
便利だからな〜
code:本来.php
Debugbar::debug([
'savedata' => $savedata,
'selected_info_count' => count($selected_info),
]);
Loose comparison using
多分level5のエラー(10は、そもそも==が使えないので。)
code:php
if ($data->is_enemy == false) {
#:415 Loose comparison using == between 0|0.0|''|'0'|array{}|false|null and false will always evaluate to true.
🪪 equal.alwaysTrue
緩い比較のため、だいたいtrueになっちゃうよというエラー
===で書くか、複数条件としてもう少し固めると良い
Access to an undefined property
リレーションを使っている場所で出る
code:php
Line Models/Game/Rpg/Party.php
------ --------------------------------------------------------------------------------------
:292 Access to an undefined property App\Models\Game\Rpg\Party::$role.
🪪 property.notFound
💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
code:php
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @return belongsTo<Role, $this>
*/
public function role(): BelongsTo
{
// 子側は自分の持つカラムを指定して、相手の主キーと紐づける
return $this->belongsTo(Role::class, 'role_id');
}
$role_class = $party->role->class;
ちゃんとPHPDocを書くとよい
Binary operation ".=" between mixed and ',' results in an error.
そのまま、mixedであることが原因
code:php
$data .= ' + etc';
$dataがmixedなので、文字列が連結できないという感じ。証明してやると良い。